package common

import (
	"context"
	"encoding/json"
	"gitee.com/kinwyb/appTools/log"
	"github.com/opentracing/opentracing-go"
	"github.com/opentracing/opentracing-go/ext"
	opentracinglog "github.com/opentracing/opentracing-go/log"
	zipkinot "github.com/openzipkin-contrib/zipkin-go-opentracing"
	"github.com/openzipkin/zipkin-go"
	zipkinhttp "github.com/openzipkin/zipkin-go/reporter/http"
	"go.elastic.co/apm"
	"go.elastic.co/apm/module/apmot"
	"go.elastic.co/apm/transport"
	"io"
	logNav "log"
	"net/http"
	"net/url"
	"os"
	"time"
)

// Tracing 开启追踪
var Tracing = false

const TracingChild = 1
const TracingFollowsFrom = 2
const TracingChildString = "1"
const TracingFollowsFromString = "2"

// TracingContext 追踪上下文
type TracingContext interface {
	//Start 注意:由于该方法可能会进行连级调用所以不允许返回nil
	Start(operationName string) TracingContext

	//Finish 标记追踪结束
	Finish()
}

type TracingSpan interface {
	//SpanContext
	SpanContext() opentracing.SpanContext

	//context.Context
	Context(ctx context.Context) context.Context

	//Span
	Span() opentracing.Span

	//ChildOf
	ChildOf(operationName string) TracingSpan

	//FollowsFrom
	FollowsFrom(operationName string) TracingSpan

	//日志
	Logf(fields ...opentracinglog.Field)

	//Finish
	Finish()

	//Start
	Start(operationName string) TracingSpan

	//InjectHttpHeader
	InjectHttpHeader(header http.Header)

	//InjectTextMap
	InjectTextMap(textMap map[string]string)
}

//数据
type TracingSpanImpl struct {
	//operationName   string
	rootSpanContext opentracing.SpanContext
	span            opentracing.Span
}

//SpanContext
func (t *TracingSpanImpl) SpanContext() opentracing.SpanContext {
	if t.span != nil {
		return t.span.Context()
	} else if t.rootSpanContext != nil {
		return t.rootSpanContext
	} else {
		return nil
	}
}

//context.Context
func (t *TracingSpanImpl) Context(ctx context.Context) context.Context {
	return opentracing.ContextWithSpan(ctx, t.span)
}

//Span
func (t *TracingSpanImpl) Span() opentracing.Span {
	return t.span
}

//ChildOf
func (t *TracingSpanImpl) ChildOf(operationName string) TracingSpan {
	//if t.span != nil {
	//	fmt.Println("childof=>", t.operationName, "=>", operationName)
	//}
	span := opentracing.StartSpan(operationName,
		opentracing.ChildOf(t.SpanContext()))
	return &TracingSpanImpl{
		//operationName: operationName,
		span: span,
	}
}

//FollowsFrom
func (t *TracingSpanImpl) FollowsFrom(operationName string) TracingSpan {
	//if t.span != nil {
	//	fmt.Println("FollowsFrom=>", t.operationName, "=>", operationName)
	//}
	span := opentracing.StartSpan(operationName,
		opentracing.FollowsFrom(t.SpanContext()))
	return &TracingSpanImpl{
		//operationName: operationName,
		span: span,
	}
}

//日志
func (t *TracingSpanImpl) Logf(fields ...opentracinglog.Field) {
	t.span.LogFields(fields...)
}

//Finish
func (t *TracingSpanImpl) Finish() {
	t.span.Finish()
}

//Start
func (t *TracingSpanImpl) Start(operationName string) TracingSpan {
	//if t.span != nil {
	//	fmt.Println("start=>", t.operationName, "=>", operationName)
	//}
	t.span = opentracing.StartSpan(
		operationName,
		ext.RPCServerOption(t.rootSpanContext))
	//t.operationName = operationName
	return t
}

//编码HTTP请求
func (t *TracingSpanImpl) InjectHttpHeader(header http.Header) {
	if header == nil || t.span == nil {
		return
	}
	carrier := opentracing.HTTPHeadersCarrier(header)
	t.span.Tracer().Inject(
		t.span.Context(),
		opentracing.HTTPHeaders,
		carrier)
}

func (t *TracingSpanImpl) InjectTextMap(textMap map[string]string) {
	carrier := opentracing.TextMapCarrier(textMap)
	t.span.Tracer().Inject(t.span.Context(), opentracing.TextMap, carrier)
}

//新建一个追踪
func NewTracingSpanStart(operationName string) TracingSpan {
	//fmt.Println("新数据=>", operationName)
	span := opentracing.StartSpan(operationName)
	return &TracingSpanImpl{
		//operationName: operationName,
		span: span,
	}
}

//解析一个HTTP追踪请求
func NewTracingSpanExtractHttpRequest(req *http.Request) TracingSpan {
	if !Tracing || req == nil {
		return nil
	}
	ret := &TracingSpanImpl{}
	var err error
	ret.rootSpanContext, err = opentracing.GlobalTracer().Extract(
		opentracing.HTTPHeaders,
		opentracing.HTTPHeadersCarrier(req.Header))
	if err != nil {
		span, _ := opentracing.StartSpanFromContext(req.Context(), req.URL.Path)
		if span != nil {
			ret.span = span
			return ret
		} else {
			return NewTracingSpanStart(req.URL.Path)
		}
	}
	return ret.ChildOf(req.URL.Path)
}

// NewTracingSpanExtractTextMap 解析一个HTTP追踪请求
func NewTracingSpanExtractTextMap(operationName string,
	spanMapJson string, isFollows bool) TracingSpan {
	if !Tracing {
		return nil
	}
	if spanMapJson == "" {
		return NewTracingSpanStart(operationName)
	}
	textMap := map[string]string{}
	json.Unmarshal([]byte(spanMapJson), &textMap)
	ret := &TracingSpanImpl{}
	var err error
	ret.rootSpanContext, err = opentracing.GlobalTracer().Extract(
		opentracing.TextMap,
		opentracing.TextMapCarrier(textMap))
	if err != nil {
		log.AppTool.Debugf("追踪解析错误:%s", err.Error())
	}
	if isFollows {
		return ret.FollowsFrom(operationName)
	} else {
		return ret.ChildOf(operationName)
	}
}

// StartZipkinServer 开启Zipkin服务, 返回的Close需要在结束时关闭
func StartZipkinServer(zipkinURL string, localIP string, serviceName string) io.Closer {
	lg := log.Tag("zipkin")
	reporter := zipkinhttp.NewReporter(zipkinURL,
		zipkinhttp.Timeout(30*time.Second),
		zipkinhttp.Logger(logNav.New(lg.Writer(), "[zipkin]", 0)))

	// create our local service endpoint
	endpoint, err := zipkin.NewEndpoint(serviceName, localIP)
	if err != nil {
		lg.Errorf("zipkin endpoint 创建失败: %s", err.Error())
		reporter.Close()
		Tracing = false
		return nil
	}

	// initialize our tracer
	nativeTracer, err := zipkin.NewTracer(reporter, zipkin.WithLocalEndpoint(endpoint))
	if err != nil {
		lg.Errorf("zipkin tracer 创建失败: %s", err.Error())
		reporter.Close()
		Tracing = false
		return nil
	}

	// use zipkin-go-opentracing to wrap our tracer
	tracer := zipkinot.Wrap(nativeTracer)

	// Explicitly set our tracer to be the default tracer.
	opentracing.SetGlobalTracer(tracer)
	TraceType = TraceTypeZipkin
	return reporter
}

var TraceType string

const (
	TraceTypeZipkin = "zipkin"
	TraceTypeAPM    = "APM"
)

// StartElasticAPMServer 开启elastic apm服务
func StartElasticAPMServer(serviceURL string, env string, serviceName string) io.Closer {
	urlv, err := url.Parse(serviceURL)
	if err != nil {
		log.Tag("apm").WithError(err).Error("APM服务启动失败")
		Tracing = false
		return nil
	}
	apmTransport, err := transport.NewHTTPTransport()
	if err != nil {
		log.Tag("apm").WithError(err).Error("APM服务启动失败")
		Tracing = false
		return nil
	}
	apmTransport.SetServerURL(urlv)
	apmtracer, err := apm.NewTracerOptions(apm.TracerOptions{
		ServiceName: serviceName,
		//ServiceVersion:     "",
		ServiceEnvironment: env,
		Transport:          apmTransport,
	})
	if err != nil {
		log.Tag("apm").WithError(err).Error("APM服务启动失败")
		Tracing = false
		return nil
	}
	serviceNodeName := os.Getenv("ELASTIC_APM_SERVICE_NODE_NAME")
	if serviceNodeName == "" {
		nodeName := LocalIPv4()
		if nodeName == "" {
			nodeName = serviceName + "-" + env
		}
		os.Setenv("ELASTIC_APM_SERVICE_NODE_NAME", nodeName)
	}
	tracer := apmot.New(apmot.WithTracer(apmtracer))
	// Explicitly set our tracer to be the default tracer.
	opentracing.SetGlobalTracer(tracer)

	TraceType = TraceTypeAPM
	apm.DefaultTracer = apmtracer
	log.SetElasticAPMHook()
	//return reporter
	return apmTracerCloser{apmTracer: apmtracer}
}

type apmTracerCloser struct {
	apmTracer *apm.Tracer
}

func (a apmTracerCloser) Close() error {
	if a.apmTracer != nil {
		a.apmTracer.Close()
	}
	return nil
}
